package singleton;

/**
 * @author: whh
 * @Date: 2021/5/26
 * @Description: 
 * 单例模式有如下几个特点：
 * 在java应用中，单例模式能保证在一个jvm中，该对象只有一个实例存在
 * 构造器必须是私有的，外部类无法通过构造器方法创建该实例
 * 没有公开的set方法，外部类无法通过set方法创建该实例
 * 提供一个公开的get方法获取唯一的这个实例
 * 
 * 单例模式有如下几点好处：
 * 某些类创建比较频繁，对于一些大型的对象，这是一笔很大的系统开销 
 * 省去了new操作符，降低了系统内存的使用频率，减轻GC压力
 * 系统中某些类，如spring里的controller，控制着处理流程，如果该类可以创建多个的话，系统完全乱了
 * 避免了对资源的重复占用
 *
 */
public class Singleton {
    
    /***
     * @Desc: 私有构造器
     * @param
     * @return {@link null}
     * @author wonder
     * @date 2021/5/26
     */
    private Singleton(){}


    /***
     * @Desc: 饿汉式：在别人使用前就提前把对象new出来，节省了创建类这一步开销，用空间换时间，但提前占用了资源
     * @param null
     * @return {@link null}
     * @author wonder
     * @date 2021/5/26
     */
    private static final Singleton singleton = new Singleton();

    public static Singleton getInstance(){
        return singleton;
    }

    /***
     * @Desc: 懒汉式（不安全）
     * 懒汉式大家可以理解为他懒，别人第一次调用的时候他发现自己的实例是空的，然后去初始化了，再赋值
     * @param null
     * @return {@link null}
     * @author wonder
     * @date 2021/5/26
     */
    private static Singleton SINGLETON1 = null;

    public static Singleton getInstance1(){
        if(SINGLETON1 == null){
            SINGLETON1 = new Singleton();
        }
        return SINGLETON1;
    }

    /*
     * 懒汉和饿汉的对比：
     * 大家可以发现两者的区别基本上就是第一次创作时候的开销问题，以及线程安全问题（线程不安全模式的懒汉）。
     *
     * 那有了这个对比，那他们的场景好理解了，在很多电商场景，如果这个数据是经常访问的热点数据，
     * 那我就可以在系统启动的时候使用饿汉模式提前加载（类似缓存的预热）
     * 这样哪怕是第一个用户调用都不会存在创建开销，而且调用频繁也不存在内存浪费了。
     *
     * 而懒汉式呢我们可以用在不怎么热的地方，比如那个数据你不确定很长一段时间是不是有人会调用，
     * 那就用懒汉，如果你使用了饿汉，但是过了几个月还没人调用，提前加载的类在内存中是有资源浪费的。
     */

    /*
     * 在运行过程中可能存在这么一种情况：多个线程去调用getInstance方法来获取Singleton的实例，
     * 那么就有可能发生这样一种情况，当第一个线程在执行if（instance==null）时，此时instance是为null的进入语句。
     *
     * 在还没有执行instance=new singleton.Singleton()时（此时instance是为null的）
     * 第二个线程也进入了if(instance==null)这个语句，
     * 因为之前进入这个语句的线程中还没有执行instance=new singleton.Singleton()，
     * 所以它会执行instance = new singleton.Singleton()来实例化Singleton对象，
     * 因为第二个线程也进入了if语句所以它会实例化Singleton对象。
     *
     * 这样就导致了实例化了两个Singleton对象，那怎么解决？
     */

    /***
     * @Desc: 加锁懒汉式,双重验证锁
     * @param null
     * @return {@link null}
     * @author wonder
     * @date 2021/5/26
     */
    private static Singleton SINGLETON2 = null;

    public static Singleton getInstance2(){
        if(SINGLETON2 == null){
            synchronized (Singleton.class){
                if(SINGLETON2 == null){
                    SINGLETON2 = new Singleton();
                }
            }
        }
        return SINGLETON2;
    }

    /*
     * 看下面的情况：在Java指令中创建对象和赋值操作是分开进行的，
     * 也就是说instance = new singleton.Singleton();语句是分两步执行的。
     *
     * 但是JVM并不保证这两个操作的先后顺序，
     * 也就是说有可能JVM会为新的Singleton实例分配空间，然后直接赋值给instance成员，
     * 然后再去初始化这个Singleton实例。
     *
     * volatile的作用
     * 防止指令重排序，因为instance = new singleton.Singleton()不是原子操作
     * 保证内存可见
     */

    /***
     * @Desc: 懒汉式+双重校验锁+volatile
     * 通过volatile修饰的变量，不会被线程本地缓存，
     * 所有线程对该对象的读写都会第一时间同步到主内存，从而保证多个线程间该对象的准确性
     * @param null
     * @return {@link null}
     * @author wonder
     * @date 2021/5/26
     */


    private volatile static Singleton singleton3 = null;

    public static Singleton getInstance3(){
        if(singleton3 == null){
            synchronized (Singleton.class){
                if(singleton3 == null){
                    singleton3 = new Singleton();
                }
            }
        }
        return singleton3;
    }


    /*
     * 但是由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化，所以运行效率并不是很高，还有更优的写法吗？
     * 通过静态内部类
     */


    private static class SingletonFactory{
        private static Singleton instance = new Singleton();
    }

    public static Singleton getInstance4(){
        return SingletonFactory.instance;
    }


}
